package com.huawei.mpchartlib.utils;

import com.huawei.mpchartlib.data.Entry;
import com.huawei.mpchartlib.interfaces.datasets.IBubbleDataSet;
import com.huawei.mpchartlib.interfaces.datasets.ILineDataSet;
import ohos.agp.render.Path;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;

import java.util.List;

public class Transformer {

    /**
     * matrix to map the values to the screen pixels
     */
    protected Matrix mMatrixValueToPx = new Matrix();

    /**
     * matrix for handling the different offsets of the chart
     */
    protected Matrix mMatrixOffset = new Matrix();

    protected ViewPortHandler mViewPortHandler;

    public Transformer(ViewPortHandler viewPortHandler) {
        this.mViewPortHandler = viewPortHandler;
    }

    /**
     * Prepares the matrix that transforms values to pixels. Calculates the
     * scale factors from the charts size and offsets.
     *
     * @param xChartMin
     * @param deltaX
     * @param deltaY
     * @param yChartMin
     */
    public void prepareMatrixValuePx(float xChartMin, float deltaX, float deltaY, float yChartMin) {

        float scaleX = (float) ((mViewPortHandler.contentWidth()) / deltaX);
        float scaleY = (float) ((mViewPortHandler.contentHeight()) / deltaY);

        if (Float.isInfinite(scaleX)) {
            scaleX = 0;
        }
        if (Float.isInfinite(scaleY)) {
            scaleY = 0;
        }

        // setup all matrices
        mMatrixValueToPx.reset();
        mMatrixValueToPx.postTranslate(-xChartMin, -yChartMin);
        mMatrixValueToPx.postScale(scaleX, -scaleY);
    }

    /**
     * Prepares the matrix that contains all offsets.
     *
     * @param inverted
     */
    public void prepareMatrixOffset(boolean inverted) {

        mMatrixOffset.reset();

        // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom);

        if (!inverted)
            mMatrixOffset.postTranslate(mViewPortHandler.offsetLeft(),
                    mViewPortHandler.getChartHeight() - mViewPortHandler.offsetBottom());
        else {
            mMatrixOffset
                    .setTranslate(mViewPortHandler.offsetLeft(), -mViewPortHandler.offsetTop());
            mMatrixOffset.postScale(1.0f, -1.0f);
        }
    }

    /**
     * Transforms an List of Entry into a float array containing the x and
     * y values transformed with all matrices for the SCATTERCHART.
     *
     * @param data
     * @return
     */
//    public float[] generateTransformedValuesScatter(IScatterDataSet data, float phaseX,
//                                                    float phaseY, int from, int to) {
//
//        final int count = (int) ((to - from) * phaseX + 1) * 2;
//
//        if (valuePointsForGenerateTransformedValuesScatter.length != count) {
//            valuePointsForGenerateTransformedValuesScatter = new float[count];
//        }
//        float[] valuePoints = valuePointsForGenerateTransformedValuesScatter;
//
//        for (int j = 0; j < count; j += 2) {
//
//            Entry e = data.getEntryForIndex(j / 2 + from);
//
//            if (e != null) {
//                valuePoints[j] = e.getX();
//                valuePoints[j + 1] = e.getY() * phaseY;
//            } else {
//                valuePoints[j] = 0;
//                valuePoints[j + 1] = 0;
//            }
//        }
//
//        getValueToPixelMatrix().mapPoints(valuePoints);
//
//        return valuePoints;
//    }

    protected float[] valuePointsForGenerateTransformedValuesBubble = new float[1];

    /**
     * Transforms an List of Entry into a float array containing the x and
     * y values transformed with all matrices for the BUBBLECHART.
     *
     * @param data
     * @return
     */
    public float[] generateTransformedValuesBubble(IBubbleDataSet data, float phaseY, int from, int to) {

        final int count = (to - from + 1) * 2; // (int) Math.ceil((to - from) * phaseX) * 2;

        if (valuePointsForGenerateTransformedValuesBubble.length != count) {
            valuePointsForGenerateTransformedValuesBubble = new float[count];
        }
        float[] valuePoints = valuePointsForGenerateTransformedValuesBubble;

        for (int j = 0; j < count; j += 2) {

            Entry e = data.getEntryForIndex(j / 2 + from);

            if (e != null) {
                valuePoints[j] = e.getX();
                valuePoints[j + 1] = e.getY() * phaseY;
            } else {
                valuePoints[j] = 0;
                valuePoints[j + 1] = 0;
            }
        }

        getValueToPixelMatrix().mapPoints(valuePoints);

        return valuePoints;
    }

    protected float[] valuePointsForGenerateTransformedValuesLine = new float[1];

    /**
     * Transforms an List of Entry into a float array containing the x and
     * y values transformed with all matrices for the LINECHART.
     *
     * @param data
     * @return
     */
    public float[] generateTransformedValuesLine(ILineDataSet data,
                                                 float phaseX, float phaseY,
                                                 int min, int max) {

        final int count = ((int) ((max - min) * phaseX) + 1) * 2;

        if (valuePointsForGenerateTransformedValuesLine.length != count) {
            valuePointsForGenerateTransformedValuesLine = new float[count];
        }
        float[] valuePoints = valuePointsForGenerateTransformedValuesLine;

        for (int j = 0; j < count; j += 2) {

            Entry e = data.getEntryForIndex(j / 2 + min);

            if (e != null) {
                valuePoints[j] = e.getX();
                valuePoints[j + 1] = e.getY() * phaseY;
            } else {
                valuePoints[j] = 0;
                valuePoints[j + 1] = 0;
            }
        }

        getValueToPixelMatrix().mapPoints(valuePoints);

        return valuePoints;
    }

    protected float[] valuePointsForGenerateTransformedValuesCandle = new float[1];

    /**
     * Transforms an List of Entry into a float array containing the x and
     * y values transformed with all matrices for the CANDLESTICKCHART.
     *
     * @param data
     * @return
     */
//    public float[] generateTransformedValuesCandle(ICandleDataSet data,
//                                                   float phaseX, float phaseY, int from, int to) {
//
//        final int count = (int) ((to - from) * phaseX + 1) * 2;
//
//        if (valuePointsForGenerateTransformedValuesCandle.length != count) {
//            valuePointsForGenerateTransformedValuesCandle = new float[count];
//        }
//        float[] valuePoints = valuePointsForGenerateTransformedValuesCandle;
//
//        for (int j = 0; j < count; j += 2) {
//
//            CandleEntry e = data.getEntryForIndex(j / 2 + from);
//
//            if (e != null) {
//                valuePoints[j] = e.getX();
//                valuePoints[j + 1] = e.getHigh() * phaseY;
//            } else {
//                valuePoints[j] = 0;
//                valuePoints[j + 1] = 0;
//            }
//        }
//
//        getValueToPixelMatrix().mapPoints(valuePoints);
//
//        return valuePoints;
//    }

    /**
     * transform a path with all the given matrices VERY IMPORTANT: keep order
     * to value-touch-offset
     *
     * @param path
     */
    public void pathValueToPixel(Path path) {

        path.transform(mMatrixValueToPx);
        path.transform(mViewPortHandler.getMatrixTouch());
        path.transform(mMatrixOffset);
    }

    /**
     * Transforms multiple paths will all matrices.
     *
     * @param paths
     */
    public void pathValuesToPixel(List<Path> paths) {

        for (int i = 0; i < paths.size(); i++) {
            pathValueToPixel(paths.get(i));
        }
    }

    /**
     * Transform an array of points with all matrices. VERY IMPORTANT: Keep
     * matrix order "value-touch-offset" when transforming.
     *
     * @param pts
     */

    public void pointValuesToPixel(float[] pts) {

        mMatrixValueToPx.mapPoints(pts);
        mViewPortHandler.getMatrixTouch().mapPoints(pts);
        mMatrixOffset.mapPoints(pts);
    }

    /**
     * Transform a rectangle with all matrices.
     *
     * @param r
     */
    public void rectValueToPixel(RectFloat r) {

        mMatrixValueToPx.mapRect(r);
        mViewPortHandler.getMatrixTouch().mapRect(r);
        mMatrixOffset.mapRect(r);
    }

    /**
     * Transform a rectangle with all matrices with potential animation phases.
     *
     * @param r
     * @param phaseY
     */
    public void rectToPixelPhase(RectFloat r, float phaseY) {

        // multiply the height of the rect with the phase
        r.top *= phaseY;
        r.bottom *= phaseY;

        mMatrixValueToPx.mapRect(r);
        mViewPortHandler.getMatrixTouch().mapRect(r);
        mMatrixOffset.mapRect(r);
    }

    public void rectToPixelPhaseHorizontal(RectFloat r, float phaseY) {

        // multiply the height of the rect with the phase
        r.left *= phaseY;
        r.right *= phaseY;

        mMatrixValueToPx.mapRect(r);
        mViewPortHandler.getMatrixTouch().mapRect(r);
        mMatrixOffset.mapRect(r);
    }

    /**
     * Transform a rectangle with all matrices with potential animation phases.
     *
     * @param r
     */
    public void rectValueToPixelHorizontal(RectFloat r) {

        mMatrixValueToPx.mapRect(r);
        mViewPortHandler.getMatrixTouch().mapRect(r);
        mMatrixOffset.mapRect(r);
    }

    /**
     * Transform a rectangle with all matrices with potential animation phases.
     *
     * @param r
     * @param phaseY
     */
    public void rectValueToPixelHorizontal(RectFloat r, float phaseY) {

        // multiply the height of the rect with the phase
        r.left *= phaseY;
        r.right *= phaseY;

        mMatrixValueToPx.mapRect(r);
        mViewPortHandler.getMatrixTouch().mapRect(r);
        mMatrixOffset.mapRect(r);
    }

    /**
     * transforms multiple rects with all matrices
     *
     * @param rects
     */
    public void rectValuesToPixel(List<RectFloat> rects) {

        Matrix m = getValueToPixelMatrix();

        for (int i = 0; i < rects.size(); i++)
            m.mapRect(rects.get(i));
    }

    protected Matrix mPixelToValueMatrixBuffer = new Matrix();

    /**
     * Transforms the given array of touch positions (pixels) (x, y, x, y, ...)
     * into values on the chart.
     *
     * @param pixels
     */
    public void pixelsToValue(float[] pixels) {

        Matrix tmp = mPixelToValueMatrixBuffer;
        tmp.reset();

        // invert all matrixes to convert back to the original value
        mMatrixOffset.invert(tmp);
        tmp.mapPoints(pixels);

        mViewPortHandler.getMatrixTouch().invert(tmp);
        tmp.mapPoints(pixels);

        mMatrixValueToPx.invert(tmp);
        tmp.mapPoints(pixels);
    }

    /**
     * buffer for performance
     */
    float[] ptsBuffer = new float[2];

    /**
     * Returns a recyclable MPPointD instance.
     * returns the x and y values in the chart at the given touch point
     * (encapsulated in a MPPointD). This method transforms pixel coordinates to
     * coordinates / values in the chart. This is the opposite method to
     * getPixelForValues(...).
     *
     * @param x
     * @param y
     * @return
     */
    public MPPointD getValuesByTouchPoint(float x, float y) {

        MPPointD result = MPPointD.getInstance(0, 0);
        getValuesByTouchPoint(x, y, result);
        return result;
    }

    public void getValuesByTouchPoint(float x, float y, MPPointD outputPoint) {

        ptsBuffer[0] = x;
        ptsBuffer[1] = y;

        pixelsToValue(ptsBuffer);

        outputPoint.x = ptsBuffer[0];
        outputPoint.y = ptsBuffer[1];
    }

    /**
     * Returns a recyclable MPPointD instance.
     * Returns the x and y coordinates (pixels) for a given x and y value in the chart.
     *
     * @param x
     * @param y
     * @return
     */
    public MPPointD getPixelForValues(float x, float y) {

        ptsBuffer[0] = x;
        ptsBuffer[1] = y;

        pointValuesToPixel(ptsBuffer);

        double xPx = ptsBuffer[0];
        double yPx = ptsBuffer[1];

        return MPPointD.getInstance(xPx, yPx);
    }

    public Matrix getValueMatrix() {
        return mMatrixValueToPx;
    }

    public Matrix getOffsetMatrix() {
        return mMatrixOffset;
    }

    private Matrix mMBuffer1 = new Matrix();

    public Matrix getValueToPixelMatrix() {
        mMBuffer1.setMatrix(mMatrixValueToPx);
        mMBuffer1.postConcat(mViewPortHandler.mMatrixTouch);
        mMBuffer1.postConcat(mMatrixOffset);
        return mMBuffer1;
    }

    private Matrix mMBuffer2 = new Matrix();

    public Matrix getPixelToValueMatrix() {
        getValueToPixelMatrix().invert(mMBuffer2);
        return mMBuffer2;
    }

    private void _translate(float[] points, float dx, float dy) {
        int i = 0;
        while (i < points.length) {
            points[i++] +=  dx;
            points[i++] +=  dy;
        }
    }

    private void _scale(float[] points, float sx, float sy) {
        int i = 0;
        while (i < points.length) {
            points[i++] *=  sx;
            points[i++] *=  sy;
        }
    }

    //TODO: Where to go
    /*private void changePoints(float[] points, Matrix matrix) {
        float[] m = matrix.getData();
        _scale(points, m[3], m[4]);
        _translate(points, m[7], m[8]);
    }

    private void invert(Matrix matrixSrc, Matrix matrixDst){

        float A11 = matrixSrc.getData()[matrixSrc.index(0,0)];
        float A12 = matrixSrc.getData()[matrixSrc.index(0,1)];
        float A13 = matrixSrc.getData()[matrixSrc.index(0,2)];
        float A21 = matrixSrc.getData()[matrixSrc.index(1,0)];
        float A22 = matrixSrc.getData()[matrixSrc.index(1,1)];
        float A23 = matrixSrc.getData()[matrixSrc.index(1,2)];
        float A31 = matrixSrc.getData()[matrixSrc.index(2,0)];
        float A32 = matrixSrc.getData()[matrixSrc.index(2,1)];
        float A33 = matrixSrc.getData()[matrixSrc.index(2,2)];

        //det
        float det = A11*A22*A33 + A12*A23*A31 + A13*A21*A31 - A11*A32*A23 - A12*A21*A33- A13*A22*A32;

        //adj
        float adjA11 = +(A22*A33-A23*A32);
        float adjA12 = -(A12*A33-A13*A32);
        float adjA13 = +(A12*A23-A13*A22);

        float adjA21 = -(A21*A33-A31*A23);
        float adjA22 = +(A11*A33-A13*A31);
        float adjA23 = -(A11*A23-A13*A21);

        float adjA31 = +(A21*A32-A22*A31);
        float adjA32 = -(A11*A32-A12*A31);
        float adjA33 = +(A11*A22-A12*A21);

        //invert
        float[] invert = {adjA11/det, adjA12/det,adjA13/det,adjA21/det,adjA22/det,adjA23/det,adjA31/det,adjA32/det,adjA33/det};

        matrixDst.setMatrix(0,0,invert[0]);
        matrixDst.setMatrix(0,1,invert[1]);
        matrixDst.setMatrix(0,2,invert[2]);
        matrixDst.setMatrix(1,0,invert[3]);
        matrixDst.setMatrix(1,1,invert[4]);
        matrixDst.setMatrix(1,2,invert[5]);
        matrixDst.setMatrix(2,0,invert[6]);
        matrixDst.setMatrix(2,1,invert[7]);
        matrixDst.setMatrix(2,2,invert[8]);

    }
    private void postMultiply(Matrix former, Matrix latter){
        float[] a = former.getData();
        float[] b = latter.getData();
        float[] c = new float[9];

        for(int i = 0;i<3;i++)
        {
            for(int j = 0;j<3;j++)
            {
                for(int k = 0;k<3;k++)
                {
                    c[i*3 + j] += a[i*3 + k]*b[k*3 + j];
                }
            }
        }

        latter.setMatrix(new Matrix(c));

    }*/
}
